Developing Facebook Platform Applications with Rails

Mike Mangino

Elevated Rails

Facebook Overview

Facebook Platform

An Example

A Quick Tour

Canvas Page (redux)

Profile Page

Profile Page

Invitations

Invitations

Messaging

Messaging

How it works

Your app becomes part of the Facebook page

Facebook acts as a proxy and sends requests to your application

Let’s build an application

Our application will:

Getting started

First, we need to create the application

Setting it up

We specify

The callback URL

Where Facebook will talk to our app.

Facebook must be able to talk to our server so you can:

1 Shameless plug. Check out http://tunnlr.com

Creating the Rails app

Facebooker

Facebooker is a plugin that provides Facebook support for Rails

Not the first library1 for Ruby, but the one with the most idiomatic interface.

1 That was RFacbeook which is no longer maintained

Main Developers

A Quick Break

Facebooker.yml

Contains the configuration info for our app, including keys


development:
  api_key: e34ec276c93b8b443fd15691c57908c5
  secret_key: 14d51082e7c88a23f86b458017abd912
  canvas_page_name: mangino
  callback_url: http://web1.tunnlr.com:10103
  tunnel:
    public_host_username: tunnlr7
    public_host: ssh1.tunnlr.com
    public_port: 10103
    local_port: 3000

TSATCPW[1]


  map.resources :conference_sessions
  map.root :controller=>"conference_sessions"


  def index
    render :text=>"

Hello!

" end

1 The Simplest Application That Could Possibly Work

Give it a try!

Drumroll please….

What was that <fb:name> thing?

Along with providing a REST API for accessing data about users, Facebook provides FBML, the Facebook Markup Language.

FBML is:

That looked just like a normal Rails App

It did.

It is.

Back to our list

Let’s start building our application. We’ll tackle the first item first

Profile updates require installation

Most applications require installation. Installation grants certain permissions:

Users may disable some or all permissions

Installation

Requiring installation

To require an application to be installed, we type the following1


class ApplicationController < ActionController::Base
  helper :all # include all helpers, all the time
  ensure_application_is_installed_by_facebook_user

1 Thanks Chad!

Let’s do some coding

Once a user has our application installed, Facebook will provide the fb_sig_user parameter to tell us the ID.[1]

We also get a session key which we will need to access the REST API.

Let’s implement a simple User model that:

1 We can trust this parameter because Facebook signs all requests

A Simple User model


  def self.for(facebook_id, facebook_session = nil)
    returning User.find_or_create_by_facebook_id(facebook_id) do |user|
      unless facebook_session.nil?
        user.store_session(facebook_session.session_key)
      end
    end
  end

  def facebook_session
    @facebook_session ||= 
      returning Facebooker::Session.create do |session|
        session.secure_with!(session_key, facebook_id, 1.day.from_now)
      end
    end
  
  def store_session(session_key)
    if self.session_key != session_key
      update_attribute(:session_key, session_key)
    end
  end

User Model Concepts

1 You’ll need the MySQL BigInt plugin to support MySQL

Integrate with Controller


  helper_attr :current_user
  
  attr_accessor :current_user
  before_filter :set_current_user  

  def set_current_user
    set_facebook_session
    if facebook_session and facebook_session.secured? and facebook_session.user
      self.current_user = User.for(facebook_session.user.to_i,facebook_session) 
    end
  end

Controller Concepts

How does that help us?

Now that we have the basics in place, we can update our users profile

Our profile update code


  def update_profile
    facebook_session.profile_fbml=
      ""
  end

Talk about a leaky abstraction!

This is good enough for now. We’ll fix it in a bit.

Back to our list

Let’s look at browsing the schedule

First, we need some models

Luckily, I happen to have a ConferenceSession model over here.

It even knows how to import itself


  def self.import
    doc=Hpricot(open("http://en.oreilly.com/rails2008/public/schedule/full"))
    doc.search(".en_schedule_day").each do |day|
      start_date = Date.parse(day.search("h2").inner_text)
      day.search(".en_schedule_time").each do |time|
        start_time=time.search("h3").inner_text
        time.search(".en_session") do |ses|
          s=ConferenceSession.new
      
          s.start_date=start_date
          s.start_time=start_time
        
          s.title=ses.search(".en_session_title").inner_text
          s.room=ses.search(".location").first.inner_text
          s.authors = ses.search(".en_session_speakers").inner_text
          s.description = ses.search(".en_session_description").inner_text
          s.save
        end
      end
    end
  end

On to the Controller

Our controller will get a list of sessions to display:


  def index
    @dates = ConferenceSession.dates
    @date = params[:date].blank? ? @dates.first  : Date.parse(params[:date])
    @time_table  = TimeTable.new(ConferenceSession.for(@date),:starts_at,5)
  end

Now the view

Let’s start with an HTML view



    <%  for header in @time_table.headers %>
      
    <% end %>
  
  <% @time_table.rows.each_with_index do |row,idx|%>
  
    <% for entry in row %>
      <% unless entry.nil?%>
        
      <% end %>
    <% end %>
    
  <% end %>
Time<%=header %>
<%= @time_table.formatted_times[idx] if ((idx % 3) == 0) %>= render :partial=>"conference_session",:locals=>{:conference_session=>entry.payload} unless entry.payload.nil?>

Will that work?

Yes!

Most HTML can be used as FBML

CSS style works, but URLs must be absolute

Flash and embedded objects require special handling

It’s Ugly

I’m no UI designer, but there are some things we can do to make it look a little better. FBML provides Facebook style UI primitives.

Let’s add a layout.


<%=stylesheet_link_tag "application"%>



<% fb_tabs do %> <% for date in @dates %> <%= fb_tab_item date.to_s,root_path(:date=>date),:selected=>date == @date%> <% end %> <%= fb_tab_item "Invite Friends",new_invitation_path,:selected=>@invitation%> <% end %> <%= facebook_messages%> <%= yield%>

Better

FBML

FBML is HTML like.

<fb:tabs> creates a tab navigation

<fb:tab-item> creates a tab

FBML is turned into HTML by Facebook

Most FBML tags have Rails helpers

FBML

FBML is context aware

Different content is show depending upon the viewer

FBML

FBML is presentation heavy

It also has flow control

FBML

There are also pre-built tools

The Canvas Page

Coding the canvas page is mostly like any other rails app.

Let’s add buttons to each session to say that you will attend it

The Models

script/generate model attendance


  def will_attend(session)
    attendances.create!(:conference_session=>session)
    UserPublisher.deliver_attendance(self,session) rescue nil
  end
  
  def wont_attend(session)
    cs=attending?(session)
    cs.destroy unless cs.nil?
  end
  
  def attending?(session)
    attendances.detect {|a| a.conference_session_id==session.id}
  end

The Controller

script/generate controller attendances


class AttendancesController < ApplicationController
  
  def create
    conference_session=ConferenceSession.find(params[:conference_session_id])
    current_user.will_attend(conference_session)
    redirect_to root_path(:date=>conference_session.start_date)
  end
  
  def destroy
    a=Attendance.find(params[:id])
    current_user.wont_attend(a.conference_session)
    redirect_to root_path(:date=>a.conference_session.start_date)
  end
end

Another little break

Making it Social

So far, we’ve just built a Rails Application. Now, let’s make it social. We will publish a Feed item when somebody says they will attend a session.

Publisher

The Facebooker Publisher is very similar to action mailer

UserPublisher.deliver_attendance

You can send:

Creating a Publisher

script/generate publisher User


  def attendance(user,session)
    send_as :action
    from user.facebook_session.user
    title "#{fb_name(user)} is attending #{truncate(session.title,20)}"
    body "Set your schedule for #{link_to "RailsConf", root_url}"
  end

Results in

Feed items

Feed Items are shown:

How to Spread?

Since Feed Items only go to people who already have the application, we need another way to spread. We could send emails to our friends, but that seems tacky. Let’s invite them to use the application.

Here’s what we want to build:

It’s Easy


<% fb_multi_friend_request("RailsConf","Who do you want to invite to RailsConf?",invitations_path) do %>
  I'm attending RailsConf. Join me to share your schedule!
  <%= fb_req_choice("Add Railsconf",root_url(:canvas=>true)) %>
<% end %>

Invites

Yet Another little break

Make it social

Along with messaging and invites, we can use the friend graph to show good information.

Facebook sends the list of a user’s friends on each request.

Let’s show which of your friends are attending each session

Adding to the model

We’ll need a method to tell us which friends are attending. Let’s add this to ConferenceSession


  def friends_attending(facebook_id_string,limit=nil)
    facebook_id_string||=""
    facebook_ids = facebook_id_string.split(/,/)
    base_options = {}
    base_options[:limit]=limit unless limit.nil?
    User.find(:all,base_options.merge(
              :joins=>:attendances,
              :conditions=>["facebook_id in (?) and conference_session_id=?",facebook_ids,id]))
  end

The View

Facebook sends the list of friends on each request. We can use that to look up who is attending each session.



  <% for friend in @friends_attending %>
    
  <% end %>  
  
  <% for user in @attendees %>
    
  <% end %>  
Friends Attending
<= fb_profile_pic(friend)><= fb_name friend,:use_you=>false>
Others Attending
<= fb_profile_pic(user)><= fb_name user,:use_you=>false>

The Social Graph

One of the most powerful parts of Facebook is the social graph. Use it!

Updating the Profile

Earlier, we performed a really simple profile update. Let’s add more detail

We will show the sessions that you will attend.

We saw the Facebooker Publisher earlier. We’ll use it again here.

Profile Update


  def profile_update(user)
    send_as :profile
    recipients user.facebook_session.user
    profile render(:partial=>"profile",:assigns=>{:user=>user})
  end

Like ActionMailer, the publisher allows you to render templates

Profile View



 <= image_tag "profile_badge">
 

<=fb_name "profileowner",:use_you=>false> is attending:

<% for sess in @user.conference_sessions %> <= render :partial=>"conference_session",:object=>sess> <% end %>

<% conference_session ||= @conference_session %>

<=conference_session.title>

<=conference_session.start_time>, <=conference_session.room>

<= pluralize(conference_session.attendances.count,"person")> attending

Lookin’ Good!

There’s something that concerns me. We’ll need to do a lot of profile updates.

The number of people attending will require a profile update every time another person says they will attend that session.

DRY it up with REFs

Facebook REFs are like a persistent, write only memcached.

You set content with the publisher and then can reference that content from other places.

Let’s change that code to use refs

Our Profile On REFs



 <= image_tag "profile_badge">
 

<=fb_name "profileowner",:use_you=>false> is attending:

<% for sess in @user.conference_sessions %>

Now we need to create the REFs


  def conference_session_ref(conference_session)
    from User.admin.facebook_session.user
    send_as :ref
    handle "conference_session_#{conference_session.id}"
    fbml render(:partial=>"conference_session",:assigns=>{:conference_session=>conference_session})
  end

Calling it


  def will_attend(session)
    attendances.create!(:conference_session=>session)
    UserPublisher.deliver_attendance(self,session) rescue nil
    UserPublisher.deliver_conference_session_ref(session)
  end

Now, we store the session information in one place. Profiles will be kept up to date with out us doing any work.

Uses for REFs

Ref Cons

Our final break

Other performance tips

Be careful with queries

Our code to find friends who are also attending a session is horrible for performance.

Instead, highlight the record if any friend is attending and show the list in the details

Denormalize for performance. Include facebook_id column in conference session records

Be Stingy with the REST API

The REST API is a little slow. Avoid it where possible

Move messaging and profile updates our of the request flow using something like Starling

Use the Batch API

Final Shameless Plug

Developing Facebook Platform Applications with Rails

In Beta Now!